#pragma once

#include <http.hpp>

namespace leak::utils::base64 {

static std::string const base64_chars =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    "abcdefghijklmnopqrstuvwxyz"
    "0123456789+/";

static inline bool IsBase64(unsigned char c) {
    return (c == 43 ||               // +
            (c >= 47 && c <= 57) ||  // /-9
            (c >= 65 && c <= 90) ||  // A-Z
            (c >= 97 && c <= 122));  // a-z
}

inline std::string Encode(unsigned char const* input, size_t len) {
    std::string   ret;
    int           i = 0;
    int           j = 0;
    unsigned char char_array_3[3];
    unsigned char char_array_4[4];

    while (len--) {
        char_array_3[i++] = *(input++);
        if (i == 3) {
            char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
            char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
                              ((char_array_3[1] & 0xf0) >> 4);
            char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
                              ((char_array_3[2] & 0xc0) >> 6);
            char_array_4[3] = char_array_3[2] & 0x3f;

            for (i = 0; (i < 4); i++) {
                ret += base64_chars[char_array_4[i]];
            }
            i = 0;
        }
    }

    if (i) {
        for (j = i; j < 3; j++) {
            char_array_3[j] = '\0';
        }

        char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
        char_array_4[1] = ((char_array_3[0] & 0x03) << 4) +
                          ((char_array_3[1] & 0xf0) >> 4);
        char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) +
                          ((char_array_3[2] & 0xc0) >> 6);
        char_array_4[3] = char_array_3[2] & 0x3f;

        for (j = 0; (j < i + 1); j++) {
            ret += base64_chars[char_array_4[j]];
        }

        while ((i++ < 3)) {
            ret += '=';
        }
    }

    return ret;
}

inline std::string Encode(std::string const& input) {
    return Encode(
        reinterpret_cast<const unsigned char*>(input.data()),
        input.size());
}

inline std::string Decode(std::string const& input) {
    size_t        in_len = input.size();
    int           i      = 0;
    int           j      = 0;
    int           in_    = 0;
    unsigned char char_array_4[4], char_array_3[3];
    std::string   ret;

    while (in_len-- && (input[in_] != '=') && IsBase64(input[in_])) {
        char_array_4[i++] = input[in_];
        in_++;
        if (i == 4) {
            for (i = 0; i < 4; i++) {
                char_array_4[i] = static_cast<unsigned char>(base64_chars.find(char_array_4[i]));
            }

            char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
            char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
            char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

            for (i = 0; (i < 3); i++) {
                ret += char_array_3[i];
            }
            i = 0;
        }
    }

    if (i) {
        for (j = i; j < 4; j++)
            char_array_4[j] = 0;

        for (j = 0; j < 4; j++)
            char_array_4[j] = static_cast<unsigned char>(base64_chars.find(char_array_4[j]));

        char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
        char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
        char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

        for (j = 0; (j < i - 1); j++) {
            ret += static_cast<std::string::value_type>(char_array_3[j]);
        }
    }

    return ret;
}

}  // namespace leak::utils::base64

namespace leak::utils::sha1 {

namespace {

    // Rotate an integer value to left.
    inline unsigned int rol(unsigned int value, unsigned int steps) {
        return ((value << steps) | (value >> (32 - steps)));
    }

    // Sets the first 16 integers in the buffert to zero.
    // Used for clearing the W buffert.
    inline void clearWBuffert(unsigned int* buffert) {
        for (int pos = 16; --pos >= 0;) {
            buffert[pos] = 0;
        }
    }

    inline void innerHash(unsigned int* result, unsigned int* w) {
        unsigned int a = result[0];
        unsigned int b = result[1];
        unsigned int c = result[2];
        unsigned int d = result[3];
        unsigned int e = result[4];

        int round = 0;

#define sha1macro(func, val)                                            \
    {                                                                   \
        const unsigned int t = rol(a, 5) + (func) + e + val + w[round]; \
        e                    = d;                                       \
        d                    = c;                                       \
        c                    = rol(b, 30);                              \
        b                    = a;                                       \
        a                    = t;                                       \
    }

        while (round < 16) {
            sha1macro((b & c) | (~b & d), 0x5a827999)++ round;
        }
        while (round < 20) {
            w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
            sha1macro((b & c) | (~b & d), 0x5a827999)++ round;
        }
        while (round < 40) {
            w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
            sha1macro(b ^ c ^ d, 0x6ed9eba1)++ round;
        }
        while (round < 60) {
            w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
            sha1macro((b & c) | (b & d) | (c & d), 0x8f1bbcdc)++ round;
        }
        while (round < 80) {
            w[round] = rol((w[round - 3] ^ w[round - 8] ^ w[round - 14] ^ w[round - 16]), 1);
            sha1macro(b ^ c ^ d, 0xca62c1d6)++ round;
        }

#undef sha1macro

        result[0] += a;
        result[1] += b;
        result[2] += c;
        result[3] += d;
        result[4] += e;
    }

}  // namespace

inline void Calc(void const* src, size_t bytelength, unsigned char* hash) {
    // Init the result array.
    unsigned int result[5] = { 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 };

    // Cast the void src pointer to be the byte array we can work with.
    unsigned char const* sarray = (unsigned char const*)src;

    // The reusable round buffer
    unsigned int w[80];

    // Loop through all complete 64byte blocks.

    size_t endCurrentBlock;
    size_t currentBlock = 0;

    if (bytelength >= 64) {
        size_t const endOfFullBlocks = bytelength - 64;

        while (currentBlock <= endOfFullBlocks) {
            endCurrentBlock = currentBlock + 64;

            // Init the round buffer with the 64 byte block data.
            for (int roundPos = 0; currentBlock < endCurrentBlock; currentBlock += 4) {
                // This line will swap endian on big endian and keep endian on
                // little endian.
                w[roundPos++] = (unsigned int)sarray[currentBlock + 3] | (((unsigned int)sarray[currentBlock + 2]) << 8) | (((unsigned int)sarray[currentBlock + 1]) << 16) | (((unsigned int)sarray[currentBlock]) << 24);
            }
            innerHash(result, w);
        }
    }

    // Handle the last and not full 64 byte block if existing.
    endCurrentBlock = bytelength - currentBlock;
    clearWBuffert(w);
    size_t lastBlockBytes = 0;
    for (; lastBlockBytes < endCurrentBlock; ++lastBlockBytes) {
        w[lastBlockBytes >> 2] |= (unsigned int)sarray[lastBlockBytes + currentBlock] << ((3 - (lastBlockBytes & 3)) << 3);
    }

    w[lastBlockBytes >> 2] |= 0x80 << ((3 - (lastBlockBytes & 3)) << 3);
    if (endCurrentBlock >= 56) {
        innerHash(result, w);
        clearWBuffert(w);
    }
    w[15] = bytelength << 3;
    innerHash(result, w);

    // Store hash in result pointer, and make sure we get in in the correct
    // order on both endian models.
    for (int hashByte = 20; --hashByte >= 0;) {
        hash[hashByte] = (result[hashByte >> 2] >> (((3 - hashByte) & 0x3) << 3)) & 0xff;
    }
}

}  // namespace leak::utils::sha1

namespace leak::net::ws {

enum Opcode {
    WS_OPCODE_CONTINUE = 0x0,
    WS_OPCODE_TEXT     = 0x1,
    WS_OPCODE_BINARY   = 0x2,
    WS_OPCODE_CLOSE    = 0x8,
    WS_OPCODE_PING     = 0x9,
    WS_OPCODE_PONG     = 0xa,
};

enum CloseCode {
    CLOSE_NORMAL_CLOSURE             = 1000,
    CLOSE_GOING_AWAY                 = 1001,
    CLOSE_PROTOCOL_ERROR             = 1002,
    CLOSE_UNSUPPORTED_DATA           = 1003,
    CLOSE_NO_STATUS_RECEIVED         = 1005,
    CLOSE_ABNORMAL_CLOSURE           = 1006,
    CLOSE_INVALID_FRAME_PAYLOAD_DATA = 1007,
    CLOSE_POLICY_VIOLATION           = 1008,
    CLOSE_MESSAGE_TOOBIG             = 1009,
    CLOSE_MANDATORY_EXTENSION        = 1010,
    CLOSE_INTERNAL_SERVER_ERR        = 1011,
    CLOSE_SERVICE_RESTART            = 1012,
    CLOSE_TRY_AGAIN_LATER            = 1013,
    CLOSE_TLS_HANDSHAKE              = 1015,
};

enum class ConnState {
    WS_CONN_CONNECTING = 0,
    WS_CONN_OPEN       = 1,
    WS_CONN_CLOSING    = 2,
    WS_CONN_CLOSED     = 3
};

enum CloseReason {
    WS_CLOSE_NORMAL           = 1000,
    WS_CLOSE_GOING_AWAY       = 1001,
    WS_CLOSE_PROTOCOL_ERROR   = 1002,
    WS_CLOSE_NOT_ALLOWED      = 1003,
    WS_CLOSE_RESERVED         = 1004,
    WS_CLOSE_NO_CODE          = 1005,
    WS_CLOSE_DIRTY            = 1006,
    WS_CLOSE_WRONG_TYPE       = 1007,
    WS_CLOSE_POLICY_VIOLATION = 1008,
    WS_CLOSE_MESSAGE_TOO_BIG  = 1009,
    WS_CLOSE_UNEXPECTED_ERROR = 1011
};

/// Maximum size of a basic WebSocket payload
static const uint64_t PAYLOAD_SIZE_BASIC = 125;  // 2^7 -1

/// Maximum size of an extended WebSocket payload (basic payload = 126)
static const uint64_t PAYLOAD_SIZE_EXTENDED = 0xFFFF;  // 2^16 - 1, 65535

/// Maximum size of a jumbo WebSocket payload (basic payload = 127)
static const uint64_t PAYLOAD_SIZE_JUMBO = 18446744073709551615U;  // 2^63 -1

struct Header {
public:
    Header() { reset(); }

    Header(bool mask, size_t len) {
        reset();
        if (len <= PAYLOAD_SIZE_BASIC) {
            payload_len = len;
        } else if (len > PAYLOAD_SIZE_BASIC && len <= PAYLOAD_SIZE_EXTENDED) {
            payload_len      = 126;
            payload_len_ext1 = (uint16_t)len;
        } else if (len > PAYLOAD_SIZE_EXTENDED && len <= PAYLOAD_SIZE_JUMBO) {
            payload_len      = 127;
            payload_len_ext2 = len;
        } else {
            // TODO: over 64bit
        }

        if (mask) {
            for (size_t i = 0; i < 4; i++) {
                mask_key[i] = utils::GetRandomNumber(0, 255);
            }
        }
    }

    void reset() {
        fin              = 0;
        rsv1             = 0;
        rsv2             = 0;
        rsv3             = 0;
        opcode           = 0;
        mask             = 0;
        payload_len      = 0;
        payload_len_ext1 = 0;
        payload_len_ext2 = 0;

        memset(mask_key, 0, 4);
    }

    size_t len() {
        size_t len = 2;
        if (payload_len == 126) {
            len += 2;
        } else if (payload_len == 127) {
            len += 8;
        }
        if (mask) {
            len += 4;
        }
        return len;
    }

    bool Decode(const uint8_t* data, size_t len) {
        if (len < 2) return false;
        fin    = (data[0] >> 7) & 0x1;
        rsv1   = (data[0] >> 6) & 0x1;
        rsv2   = (data[0] >> 5) & 0x1;
        rsv3   = (data[0] >> 4) & 0x1;
        opcode = data[0] & 0xf;

        mask        = (data[1] >> 7) & 0x1;
        payload_len = data[1] & 0x7f;

        size_t mask_index = 2;
        if (payload_len <= 125) {
            payload_len_ext1 = 0;
            payload_len_ext2 = 0;
        } else if (payload_len == 126) {
            if (len < 4) return false;
            payload_len_ext1 = (data[2] << 8) | data[3];
            payload_len_ext2 = 0;
            mask_index       = 4;
        } else if (payload_len_ext2 == 127) {
            if (len < 10) return false;
            payload_len_ext1 = 0;
            payload_len_ext2 = ((uint64_t)data[2] << 56) |
                               ((uint64_t)data[3] << 48) |
                               ((uint64_t)data[4] << 40) |
                               ((uint64_t)data[5] << 32) |
                               ((uint64_t)data[6] << 24) |
                               ((uint64_t)data[7] << 16) |
                               ((uint64_t)data[8] << 8) |
                               ((uint64_t)data[9]);
            mask_index = 10;
        }
        if (mask) {
            if (len < mask_index + 4) return false;
            mask_key[0] = data[mask_index];
            mask_key[1] = data[mask_index + 1];
            mask_key[2] = data[mask_index + 2];
            mask_key[3] = data[mask_index + 3];
        }
        return true;
    }

    uint8_t  fin : 1;
    uint8_t  rsv1 : 1;
    uint8_t  rsv2 : 1;
    uint8_t  rsv3 : 1;
    uint8_t  opcode : 4;
    uint8_t  mask : 1;
    uint8_t  payload_len : 7;
    uint16_t payload_len_ext1;
    uint64_t payload_len_ext2;
    char     mask_key[4];
};

struct Handlers {
    using Socket = std::shared_ptr<net::tcp::Socket>;
    std::function<void(Socket)>                   connect_handler;
    std::function<void(Socket, std::string_view)> message_handler;
    std::function<void(Socket, std::string_view)> error_handler;
};

struct ServerData {
    bool      handshake;
    Handlers& handlers;
    ConnState state;

    http::RequestParser httpparser;
    std::string         protocol;
    std::string         path;
};

struct ClientData {
    bool      handshake;
    Handlers  handlers;
    ConnState state;

    http::ResponseParser httpparser;
    std::string          protocol;
};

template <bool is_client>
inline void Send(std::shared_ptr<net::tcp::Socket> socket, std::string_view data, Opcode op = WS_OPCODE_BINARY) {
    Header header(is_client, data.size());
    header.fin    = 1;
    header.opcode = op;

    char* header_out = socket->Prepare(header.len() + data.size());

    size_t offset        = 0;
    header_out[offset++] = (header.fin << 7) | (header.rsv1 << 6) | (header.rsv2 << 5) | (header.rsv3 << 4) | header.opcode;
    header_out[offset++] = (header.mask << 7) | header.payload_len;
    if (header.payload_len == 126) {
        header_out[offset++] = (header.payload_len_ext1 >> 8) & 0xff;
        header_out[offset++] = header.payload_len_ext1 & 0xff;
    } else if (header.payload_len == 127) {
        header_out[offset++] = (header.payload_len_ext2 >> 56) & 0xff;
        header_out[offset++] = (header.payload_len_ext2 >> 48) & 0xff;
        header_out[offset++] = (header.payload_len_ext2 >> 40) & 0xff;
        header_out[offset++] = (header.payload_len_ext2 >> 32) & 0xff;
        header_out[offset++] = (header.payload_len_ext2 >> 24) & 0xff;
        header_out[offset++] = (header.payload_len_ext2 >> 16) & 0xff;
        header_out[offset++] = (header.payload_len_ext2 >> 8) & 0xff;
        header_out[offset++] = header.payload_len_ext2 & 0xff;
    }

    if (header.mask) {
        header_out[offset++] = header.mask_key[0];
        header_out[offset++] = header.mask_key[1];
        header_out[offset++] = header.mask_key[2];
        header_out[offset++] = header.mask_key[3];
        char* payload_out    = socket->Prepare(data.size());
        for (size_t i = 0; i < data.size(); i++) {
            payload_out[i] = data[i] ^ header.mask_key[i % 4];
        }
    } else {
        socket->PrepareWrite(data);
    }

    socket->Flush(op == WS_OPCODE_CLOSE);
}

template <bool is_client>
inline void Close(std::shared_ptr<net::tcp::Socket> socket, uint16_t code, std::string_view reason) {
    std::string data;
    data.resize(sizeof(code) + reason.size());
    char* address = const_cast<char*>(data.data());
    address[0]    = code >> 8 & 0xff;
    address[1]    = code & 0xff;
    memcpy(address + sizeof(code), reason.data(), reason.size());
    Send<is_client>(socket, data, WS_OPCODE_CLOSE);
}

inline void HandshakeRequest(std::shared_ptr<net::tcp::Socket> socket) {
    http::Request request;
    request.method = "GET";

    socket->PrepareWrite("GET / HTTP/1.1\r\n");
    socket->PrepareWrite("origin: http://localhost:8080\r\n");
    socket->PrepareWrite("Connection: Upgrade\r\n");
    socket->PrepareWrite("Upgrade: websocket\r\n");
    socket->PrepareWrite("Sec-WebSocket-Version: 13\r\n");
    socket->PrepareWrite("Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n");
    socket->PrepareWrite("\r\n");
    socket->Flush();
}

inline size_t HandlerHandshakeRequest(std::shared_ptr<net::tcp::Socket> socket, std::string_view data) {
    auto info = socket->data<ServerData>();
    if (!info->httpparser.Parse(data)) {
        // bad handshake
        auto response = std::make_shared<http::ResponseWriter>(socket, true);

        response->status        = 400;
        response->status_string = "Bad Request";
        response->Write();
        return -1;
    } else if (info->httpparser.IsFinish()) {
        auto& request  = info->httpparser.request();
        auto  response = std::make_shared<http::ResponseWriter>(socket, true);
        if (!request.headers.count("Sec-WebSocket-Key") ||
            !request.headers.count("Sec-WebSocket-Version") ||
            !request.headers.count("Upgrade")) {
            response->status        = 400;
            response->status_string = "Bad Request";
            response->Write();
            return -1;
        }

        if (request.headers.count("Sec-WebSocket-Protocol") != 0) {
            info->protocol = request.headers.find("Sec-WebSocket-Protocol")->second;
        }
        std::string_view key     = request.headers.find("Sec-WebSocket-Key")->second;
        std::string_view version = request.headers.find("Sec-WebSocket-Version")->second;
        std::string_view upgrade = request.headers.find("Upgrade")->second;

        if (request.method != "GET" || upgrade != "websocket") {
            response->status        = 400;
            response->status_string = "Bad Request";
            response->Write();
            return -1;
        }

        info->path      = request.path;
        info->state     = ConnState::WS_CONN_OPEN;
        info->handshake = true;

        unsigned char message_digest[20];
        utils::sha1::Calc(key.data(), key.size(), message_digest);
        key = utils::base64::Encode(message_digest, 20);

        response->set_close(false);
        response->status        = 101;
        response->status_string = "Switching Protocols";
        response->headers.emplace("Upgrade", "websocket");
        response->headers.emplace("Connection", "Upgrade");
        response->headers.emplace("Sec-WebSocket-Version", "13");
        response->headers.emplace("Sec-WebSocket-Protocol", info->protocol);
        response->headers.emplace("Sec-WebSocket-Accept", key);
        response->Write();

        if (info->handlers.connect_handler)
            std::invoke(info->handlers.connect_handler, socket);
    }
    return 0;
}

inline size_t HandlerHandshakeResponse(std::shared_ptr<net::tcp::Socket> socket, std::string_view data) {
    auto info = socket->data<ClientData>();
    if (!info->httpparser.Parse(data)) {
        socket->close();
        if (info->handlers.error_handler)
            std::invoke(info->handlers.error_handler, socket, "Bad handshake response");
        return -1;
    } else if (info->httpparser.IsFinish()) {
        auto& response = info->httpparser.response();
        if (response.status != 101) {
            if (info->handlers.error_handler)
                std::invoke(info->handlers.error_handler, socket, response.status_string);
            return -1;
        }

        auto& headers = response.headers;
        if (!headers.count("Sec-WebSocket-Accept")) {
            if (info->handlers.error_handler)
                std::invoke(info->handlers.error_handler, socket, "Missing Sec-WebSocket-Accept");
            return -1;
        }

        if (!headers.count("Upgrade") || headers.find("Upgrade")->second != "websocket") {
            if (info->handlers.error_handler)
                std::invoke(info->handlers.error_handler, socket, "Missing Upgrade");
            return -1;
        }

        if (!headers.count("Connection") || headers.find("Connection")->second != "Upgrade") {
            if (info->handlers.error_handler)
                std::invoke(info->handlers.error_handler, socket, "Missing Connection");
            return -1;
        }

        unsigned char message_digest[20];
        auto          key    = std::string("dGhlIHNhbXBsZSBub25jZQ==");
        auto          accept = headers.find("Sec-WebSocket-Accept")->second;

        utils::sha1::Calc(key.data(), key.size(), message_digest);
        if (accept != utils::base64::Encode(message_digest, 20)) {
            if (info->handlers.error_handler)
                std::invoke(info->handlers.error_handler, socket, "Invalid Sec-WebSocket-Accept");
            return -1;
        }

        // if (headers.count("Sec-WebSocket-Protocol")) {
        //     if (headers.find("Sec-WebSocket-Protocol")->second != info->protocol) {
        //         socket->close();
        //         if (info->handlers.error_handler)
        //             std::invoke(info->handlers.error_handler, socket, "Bad Sec-WebSocket-Protocol");
        //         return -1;
        //     }
        // }

        // if (headers.count("Sec-WebSocket-Extensions")) {
        //     if (headers.find("Sec-WebSocket-Extensions")->second != info->extensions) {
        //         socket->close();
        //         if (info->handlers.error_handler)
        //             std::invoke(info->handlers.error_handler, socket, "Bad Sec-WebSocket-Extensions");
        //         return -1;
        //     }
        // }
        info->state = ConnState::WS_CONN_OPEN;
        if (info->handlers.connect_handler)
            std::invoke(info->handlers.connect_handler, socket);
    }
    return 0;
}

inline size_t HandlerFrame(std::shared_ptr<net::tcp::Socket> socket, std::string_view data) {
    auto   info = socket->data<ServerData>();
    Header header;
    size_t len = data.size();
    if (!header.Decode((const uint8_t*)data.data(), len)) return 0;
    if (len < header.len() || header.fin == 0) return 0;

    // len
    size_t payload_len = header.payload_len;
    if (header.payload_len_ext1 != 0) {
        payload_len = header.payload_len_ext1;
    } else if (header.payload_len_ext2 != 0) {
        payload_len = header.payload_len_ext2;
    }
    // mask
    std::string frame;
    for (size_t i = 0; i < payload_len; i++) {
        char byte = data[header.len() + i] ^ header.mask_key[i % 4];
        frame.append(&byte, 1);
    }

    // Op message
    if (header.opcode == Opcode::WS_OPCODE_CONTINUE ||
        header.opcode == Opcode::WS_OPCODE_BINARY ||
        header.opcode == Opcode::WS_OPCODE_TEXT) {
        if (info->handlers.message_handler) {
            info->handlers.message_handler(socket, frame);
        }
    } else if (header.opcode == Opcode::WS_OPCODE_CLOSE) {
        if (len - header.len() < 2) {
            socket->close();
            return len;
        }

        uint16_t    code = (*(uint8_t*)frame.data() << 8) | *((uint8_t*)frame.data() + 1);
        std::string reason(frame.begin() + 2, frame.end());
        std::cout << "close code: " << code << " reason: " << reason << std::endl;
    } else if (header.opcode == Opcode::WS_OPCODE_PING) {
        // on ping
        Send<false>(socket, "", Opcode::WS_OPCODE_PONG);
    } else if (header.opcode == Opcode::WS_OPCODE_PONG) {
        // TODO: Recv pong
    } else {
        // undefined opcode
        Close<false>(socket, CLOSE_UNSUPPORTED_DATA, "Undefined opcode");
    }
    return len;
}

inline void NewServer(utils::ContextPool pool, std::string_view host, uint16_t port, Handlers handlers) {
    tcp::Server::New(pool->Next())
        ->set_context_pool(pool)
        ->Listen(host, port)
        ->Accept([handlers = std::move(handlers)](std::shared_ptr<net::tcp::Socket> socket) mutable {
            socket
                ->set_onerror([socket, &handlers](std::string_view error) {
                    if (handlers.error_handler)
                        std::invoke(handlers.error_handler, socket, error);
                })
                ->set_data(ServerData{ false, handlers })
                ->StartRead([&handlers](std::shared_ptr<net::tcp::Socket> socket, std::string_view data) mutable {
                    auto info = socket->data<ServerData>();
                    if (!info->handshake) {
                        return HandlerHandshakeRequest(socket, data);
                    } else {
                        return HandlerFrame(socket, data);
                    }
                });
        });
}

inline void Connect(asio::io_context& context, std::string_view host, uint16_t port, Handlers handlers) {
    net::tcp::Connect(
        context,
        host,
        port,
        [handlers = std::move(handlers)](std::shared_ptr<net::tcp::Socket> socket) {
            ClientData info{ false, std::move(handlers), ConnState::WS_CONN_CONNECTING };

            socket->set_data(std::move(info));
            HandshakeRequest(socket);
            socket->set_onerror([socket](std::string_view error) {
                auto info = socket->data<ClientData>();
                if (info->handlers.error_handler)
                    std::invoke(info->handlers.error_handler, socket, error);
            });
            socket->StartRead([](std::shared_ptr<tcp::Socket> socket, std::string_view data) {
                auto info = socket->data<ClientData>();
                if (!info->handshake) {
                    return HandlerHandshakeResponse(socket, data);
                } else {
                    return HandlerFrame(socket, data);
                }
            });
        },
        [](std::string_view err) {
        });
}

}  // namespace leak::net::ws
